package com.sojson.util.pay.wx.impl;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import com.alibaba.fastjson.JSONObject;
import com.alipay.api.domain.AlipayFundTransOrderQueryModel;
import com.alipay.api.response.AlipayFundTransOrderQueryResponse;
import com.ijpay.alipay.AliPayApi;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.RequestMethod;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.enums.WxApiType;
import com.ijpay.wxpay.enums.WxDomain;
import com.sojson.base.impl.BaseOrder;
import com.sojson.config.exception.TransErrorCode;
import com.sojson.config.exception.bean.Status;
import com.sojson.constant.ConstantBean;
import com.sojson.constant.ConstantCache;
import com.sojson.constant.IpConfig;
import com.sojson.project.sys.config.entity.vo.ConfigVo;
import com.sojson.util.ArithUtil;
import com.sojson.util.ExpUtil;
import com.sojson.util.StringUtil;
import com.sojson.util.SystemParamBaseUtil;
import com.sojson.util.encrypt.enums.EEncryptType;
import com.sojson.util.json.JsonUtil;
import com.sojson.util.pay.wx.WxPayUtilInterface;
import com.sojson.util.pay.wx.WxPayV2Util;
import com.sojson.util.pay.wx.WxUtil;
import com.sojson.util.pay.wx.bean.WxParamV2;
import com.sojson.util.pay.wx.bean.WxParamV3;
import com.sojson.util.pay.wx.bean.WxResponseV2;
import com.sojson.util.pay.wx.bean.WxResponseV3;
import com.sojson.util.pay.wx.bean.WxResult;
import com.sojson.util.pay.wx.bean.child.param.WxPamAmount;
import com.sojson.util.pay.wx.bean.child.param.WxPamCombinePayerInfo;
import com.sojson.util.pay.wx.bean.child.param.WxPamSubOrders;
import com.sojson.util.pay.wx.constant.WxConstant;
import com.sojson.util.pay.wx.constant.WxCurrencyType;
import com.sojson.util.xml.XmlUtil;

/**
 * 微信支付测试环境工具类
 *
 * @author liu
 * @date 2020-11-02
 */
public class WxPayTestUtil implements WxPayUtilInterface {

    private static WxPayTestUtil wxPayTestUtil;

    /** ------------------------------R:必填----------------------------- */
    /** ------------------------------C:一定条件下可选--------------------- */
    /** ------------------------------O:可选----------------------------- */

    /**
     * 调起支付
     *
     * @param order
     * @param notifyUrl
     * @return
     * @throws Exception
     */
    @Override
    public WxResult payWeb(BaseOrder order, String notifyUrl) throws Exception {
        WxResponseV3 pay = pay(order, WxConstant.FIELD_TRADE_TYPE_VALUE_NATIVE, notifyUrl);

        // 返回结果
        WxResult result = new WxResult();
        result.initNative(pay);
        return result;
    }

    /**
     * 调起合单支付
     *
     * @param orders
     * @param notifyUrl
     * @return
     * @throws Exception
     */
    @Override
    public WxResult payMergeWeb(List<? extends BaseOrder> orders, String notifyUrl) throws Exception {
        // TODO
        WxParamV3 param = new WxParamV3();

        WxResponseV3 pay = payMerge(orders, param, WxApiType.COMBINE_TRANSACTIONS_NATIVE, notifyUrl);

        // 返回结果
        WxResult result = new WxResult();
        result.initNative(pay);
        return result;
    }

    /**
     * 调起支付
     *
     * @param order
     * @param notifyUrl
     * @return
     * @throws Exception
     */
    @Override
    public WxResult payApp(BaseOrder order, String notifyUrl) throws Exception {
        WxResponseV3 pay = pay(order, WxConstant.FIELD_TRADE_TYPE_VALUE_APP, notifyUrl);

        // 返回结果
        WxResult result = new WxResult();
        result.initApp(pay);
        return result;
    }

    /**
     * 调起合单支付
     *
     * @param orders
     * @param notifyUrl
     * @return
     * @throws Exception
     */
    @Override
    public WxResult payMergeApp(List<? extends BaseOrder> orders, String notifyUrl) throws Exception {
        WxParamV3 param = new WxParamV3();

        WxResponseV3 pay = payMerge(orders, param, WxApiType.COMBINE_TRANSACTIONS_APP, notifyUrl);

        // 返回结果
        WxResult result = new WxResult();
        result.initApp(pay);
        return result;
    }

    /**
     * 调起支付
     *
     * @param order
     * @param notifyUrl
     * @return
     * @throws Exception
     */
    @Override
    public WxResult payXcx(BaseOrder order, String notifyUrl) throws Exception {
        WxResponseV3 pay = pay(order, WxConstant.FIELD_TRADE_TYPE_VALUE_JSAPI, notifyUrl);

        // 返回结果
        WxResult result = new WxResult();
        result.initXcx(pay);
        return result;
    }

    /**
     * 调起合单支付
     *
     * @param orders
     * @param notifyUrl
     * @return
     * @throws Exception
     */
    @Override
    public WxResult payMergeXcx(List<? extends BaseOrder> orders, String notifyUrl) throws Exception {
        WxPamCombinePayerInfo pamPayer = new WxPamCombinePayerInfo();
        pamPayer.setOpenid(ConstantBean.USER_PAY_SERVICE.getWxOpenid());
        WxParamV3 param = new WxParamV3();
        param.setCombinePayerInfo(pamPayer);

        WxResponseV3 pay = payMerge(orders, param, WxApiType.COMBINE_TRANSACTIONS_JS, notifyUrl);

        // 返回结果
        WxResult result = new WxResult();
        result.initXcx(pay);
        return result;
    }

    /**
     * 调起支付
     *
     * @param order
     * @param notifyUrl
     * @return
     * @throws Exception
     */
    @Override
    public WxResult payWap(BaseOrder order, String notifyUrl) throws Exception {

        WxResponseV3 pay = pay(order, WxConstant.FIELD_TRADE_TYPE_VALUE_MWEB, notifyUrl);

        // 返回结果
        WxResult result = new WxResult();
        result.initWap(pay);
        return result;
    }

    /**
     * 调起合单支付
     *
     * @param orders
     * @param notifyUrl
     * @return
     * @throws Exception
     */
    @Override
    public WxResult payMergeWap(List<? extends BaseOrder> orders, String notifyUrl) throws Exception {
        WxParamV3 param = new WxParamV3();

        WxResponseV3 pay = payMerge(orders, param, WxApiType.COMBINE_TRANSACTIONS_H5, notifyUrl);

        // 返回结果
        WxResult result = new WxResult();
        result.initWap(pay);
        return result;
    }

    /**
     * 调起支付
     *
     * @param order
     * @param payMethod 交易类型
     * @param notifyUrl 通知地址
     * @return
     * @throws Exception
     */
    private WxResponseV3 pay(BaseOrder order, String payMethod, String notifyUrl) throws Exception {
        // 获取微信接口参数
        Map<String, ConfigVo> config = SystemParamBaseUtil.gets();
        String mchid = config.get(WxConstant.MCHID_CACHE_NAME).getVal();
        String appid = config.get(WxConstant.DEFAULT_APPID_CACHE_NAME).getVal();
        String mchKey = config.get(WxConstant.APIKEY_CACHE_NAME).getVal();
        String signKey = WxPayApi.getSignKey(mchid, mchKey, null);
        Map<String, String> xmlToMap = XmlUtil.xmlToMap(signKey);
        mchKey = xmlToMap.get(WxConstant.APIKEY_SANDBOX_SIGNKEY_CACHE_NAME);

        EEncryptType md5 = EEncryptType.MD5;
        TreeMap<String, String> treeMap = new TreeMap<>();
        WxParamV2<String> param = new WxParamV2<>(treeMap);
        param.setMchId(mchid);
        param.setAppid(appid);
        param.setOutTradeNo(order.getBaseOrderNo());
        param.setTotalFee("101");
        String title = order.getBaseTitle();
        if (StringUtil.isBlankObject(title)) {
            title = config.get(ConstantCache.WEB_NAME).getVal();
        }
        param.setBody(title);
        param.setSpbillCreateIp(IpConfig.getIp());
        param.setTradeType(payMethod);
        param.setNotifyUrl(notifyUrl);
        param.setNonceStr(WxUtil.getNonceStr());
        param.setSignType(md5.getType());
        param.setSign(WxUtil.getPaySignString(treeMap, mchKey, md5));

        // 调起微信支付
        String doPostSsl = WxPayApi.pushOrder(treeMap);
        xmlToMap = XmlUtil.xmlToMap(doPostSsl);
        WxResponseV2 res = JsonUtil.jsonToObject(JsonUtil.objectToJson(xmlToMap), WxResponseV2.class);
        if (StringUtil.isBlankObject(res)) {
            ExpUtil.throwEx("调用微信统一支付接口失败！");
        }

        String returnCode = res.getReturnCode();
        // 判断通信状态码是否成功
        if (!WxConstant.SUCCESS.equals(returnCode)) {
            ExpUtil.throwEx(res.getReturnMsg());
        }

        // 判断业务状态码是否成功
        String resultCode = res.getResultCode();
        if (!WxConstant.SUCCESS.equals(resultCode)) {
            ExpUtil.throwEx(res.getErrCode() + "-" + res.getErrCodeDes());
        }

        // 设置得到的数据
        treeMap = new TreeMap<>();
        param = new WxParamV2<>(treeMap);
        param.setMchId(mchid);
        param.setAppid(resultCode);
        String sign = res.getSign();

        // 校验签名
        if (StringUtil.isBlankObject(sign) || !sign.equals(WxUtil.getPaySignString(treeMap, mchKey, md5))) {
            // ExpUtil.throwEx("统一支付接口返回的签名错误!");
        }

        // 返回结果
        WxResponseV3 parseObject = new WxResponseV3();
        parseObject.setAppid(appid);
        parseObject.setMchid(mchid);
        parseObject.setCodeUrl(res.getCodeUrl());
        parseObject.setPrepayId(res.getPrepayId());
        parseObject.setWapUrl(res.getWapUrl());
        return parseObject;
    }

    /**
     * 调起合单支付
     *
     * @param orders
     * @param param
     * @param apiType
     * @param notifyUrl
     * @return
     * @throws Exception
     */
    private WxResponseV3 payMerge(List<? extends BaseOrder> orders, WxParamV3 param, WxApiType apiType,
        String notifyUrl) throws Exception {
        // 获取微信接口参数
        Map<String, ConfigVo> config = SystemParamBaseUtil.gets();
        String mchid = config.get(WxConstant.MCHID_CACHE_NAME).getVal();
        String appid = config.get(WxConstant.DEFAULT_APPID_CACHE_NAME).getVal();
        String serialNo = config.get(WxConstant.CA_SERIALNO_CACHE_NAME).getVal();

        param.setCombineMchid(mchid);
        param.setCombineAppid(appid);
        param.setCombineOutTradeNo(orders.get(0).getBaseOrderNoShop());
        param.setNotifyUrl(notifyUrl);
        List<WxPamSubOrders> arrayList = new ArrayList<>();
        for (BaseOrder baseOrder : orders) {
            WxPamSubOrders wxPamSubOrders = new WxPamSubOrders();
            arrayList.add(wxPamSubOrders);
            wxPamSubOrders.setMchid(mchid);
            wxPamSubOrders.setOutTradeNo(baseOrder.getBaseOrderNo());
            wxPamSubOrders.setDescription(config.get(ConstantCache.WEB_NAME).getVal());
            WxPamAmount wxPamAmount = new WxPamAmount();
            wxPamAmount.setTotalAmount(ArithUtil.bigDollarToIntPenny(baseOrder.getBaseOrderTotal()));
            wxPamAmount.setCurrency(WxCurrencyType.CNY);
            wxPamSubOrders.setAmount(wxPamAmount);
        }
        param.setSubOrders(arrayList);

        // 调起微信支付
        IJPayHttpResponse v3 = WxPayApi.v3(RequestMethod.POST, WxDomain.CHINA.getType(), apiType.getType(), mchid,
            serialNo, null, WxUtil.PRIVATE_KEY, JSONObject.toJSONString(param));
        String body = v3.getBody();
        if (StringUtil.isBlankObject(body)) {
            ExpUtil.throwEx("调用微信统一支付接口失败！");
        }
        // 获取结果
        WxResponseV3 parseObject = JSONObject.parseObject(body, WxResponseV3.class);

        if (StringUtil.isNotBlank(parseObject.getCode())) {
            ExpUtil.throwEx(parseObject.getMessage());
        }
        parseObject.setAppid(appid);
        parseObject.setMchid(mchid);
        return parseObject;
    }

    /**
     * 退款
     *
     * @param order
     * @param notifyUrl
     * @return
     * @throws Exception
     */
    @Override
    public Status refund(BaseOrder order, String notifyUrl) throws Exception {
        // 获取微信接口参数
        Map<String, ConfigVo> config = SystemParamBaseUtil.gets();
        String mchid = config.get(WxConstant.MCHID_CACHE_NAME).getVal();
        String appid = config.get(WxConstant.DEFAULT_APPID_CACHE_NAME).getVal();
        String mchKey = config.get(WxConstant.APIKEY_CACHE_NAME).getVal();
        String signKey = WxPayApi.getSignKey(mchid, mchKey, null);
        Map<String, String> xmlToMap = XmlUtil.xmlToMap(signKey);
        mchKey = xmlToMap.get(WxConstant.APIKEY_SANDBOX_SIGNKEY_CACHE_NAME);

        EEncryptType md5 = EEncryptType.MD5;
        TreeMap<String, String> treeMap = new TreeMap<>();
        WxParamV2<String> param = new WxParamV2<>(treeMap);
        param.setMchId(mchid);
        param.setAppid(appid);
        param.setOutTradeNo(order.getBaseOrderNo());
        param.setTotalFee("101");
        param.setOutRefundNo(order.getBaseRefundNo());
        param.setRefundfee("101");
        param.setNotifyUrl(notifyUrl);
        param.setNonceStr(WxUtil.getNonceStr());
        param.setSignType(md5.getType());
        param.setSign(WxUtil.getPaySignString(treeMap, mchKey, md5));

        // 调起微信支付
        String doPostSsl = WxPayApi.pushOrder(treeMap);
        xmlToMap = XmlUtil.xmlToMap(doPostSsl);
        WxResponseV2 res = JsonUtil.jsonToObject(JsonUtil.objectToJson(xmlToMap), WxResponseV2.class);
        if (StringUtil.isBlankObject(res)) {
            ExpUtil.throwEx("调用微信统一支付接口失败！");
        }

        String returnCode = res.getReturnCode();
        // 判断通信状态码是否成功
        if (!WxConstant.SUCCESS.equals(returnCode)) {
            ExpUtil.throwEx(res.getReturnMsg());
        }

        // 判断业务状态码是否成功
        String resultCode = res.getResultCode();
        if (!WxConstant.SUCCESS.equals(resultCode)) {
            ExpUtil.throwEx(res.getErrCode() + "-" + res.getErrCodeDes());
        }

        // 设置得到的数据
        treeMap = new TreeMap<>();
        param = new WxParamV2<>(treeMap);
        param.setMchId(mchid);
        param.setAppid(resultCode);
        String sign = res.getSign();

        // 校验签名
        if (StringUtil.isBlankObject(sign) || !sign.equals(WxUtil.getPaySignString(treeMap, mchKey, md5))) {
            // ExpUtil.throwEx("统一支付接口返回的签名错误!");
        }

        // 返回结果
        int resCode = -1;
        String resMsg = null;
        String resSubCode = null;
        String resSubMsg = null;
        boolean isDispose = false;
        if (!WxConstant.FIELD_TRADE_STATE_VALUE_SUCCESS.equals(resultCode)
            && !WxConstant.FIELD_TRADE_STATE_VALUE_PROCESSING.equals(resultCode)) {
            resSubCode = resultCode;
            resSubMsg = res.getErrCodeDes();
            resCode = TransErrorCode.FAIL.getCode();
            resMsg = resultCode + "-" + resSubMsg;
            isDispose = true;
        } else {
            resCode = TransErrorCode.SUCCESS.getCode();
        }

        return new Status(resCode, resMsg, resSubCode, resSubMsg, isDispose);
    }

    /**
     * 关闭订单
     *
     * @param orderNo   订单号
     * @return
     * @throws Exception
     */
    @Override
    public Status close(String orderNo) throws Exception {
        // 获取微信接口参数
        Map<String, ConfigVo> config = SystemParamBaseUtil.gets();
        String mchid = config.get(WxConstant.MCHID_CACHE_NAME).getVal();
        String serialNo = config.get(WxConstant.CA_SERIALNO_CACHE_NAME).getVal();

        // 拼装参数
        return queryByOrderNo(orderNo, mchid, serialNo);
    }

    /**
     * 关闭订单
     *
     * @param payNo     微信支付单号
     * @param mchid     商户号
     * @param serialNo  商户 API 证书序列号
     * @return
     * @throws Exception
     */
    @Override
    public Status close(String orderNo, String mchid, String serialNo) throws Exception {
        WxParamV3 param = new WxParamV3();
        param.setMchid(mchid);

        IJPayHttpResponse v3Get = WxPayApi.v3(RequestMethod.POST, WxDomain.CHINA.getType(),
            String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo), mchid, serialNo, null, WxUtil.PRIVATE_KEY,
            JsonUtil.objectToJson(param));
        System.out.println(v3Get);
        return null;
    }

    /**
     * 查询支付订单
     *
     * @param orderNo   订单号
     * @return
     * @throws Exception
     */
    @Override
    public Status queryByOrderNo(String orderNo) throws Exception {
        // 获取微信接口参数
        Map<String, ConfigVo> config = SystemParamBaseUtil.gets();
        String mchid = config.get(WxConstant.MCHID_CACHE_NAME).getVal();
        String serialNo = config.get(WxConstant.CA_SERIALNO_CACHE_NAME).getVal();

        // 拼装参数
        return queryByOrderNo(orderNo, mchid, serialNo);
    }

    /**
     * 查询支付订单
     *
     * @param payNo     微信支付单号
     * @param mchid     商户号
     * @param serialNo  商户 API 证书序列号
     * @return
     * @throws Exception
     */
    @Override
    public Status queryByOrderNo(String orderNo, String mchid, String serialNo) throws Exception {
        IJPayHttpResponse v3Get = WxPayApi.v3(RequestMethod.GET, WxDomain.CHINA.getType(),
            String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo) + WxConstant.PARAM_MCHID_GET + mchid, mchid,
            serialNo, null, WxUtil.PRIVATE_KEY, "");
        System.out.println(v3Get);
        return null;
    }

    /**
     * 查询支付订单
     *
     * @param payNo 微信支付单号
     * @return
     * @throws Exception
     */
    @Override
    public Status queryByPayNo(String payNo) throws Exception {
        // 获取微信接口参数
        Map<String, ConfigVo> config = SystemParamBaseUtil.gets();
        String mchid = config.get(WxConstant.MCHID_CACHE_NAME).getVal();
        String serialNo = config.get(WxConstant.CA_SERIALNO_CACHE_NAME).getVal();

        // 拼装参数
        return queryByOrderNo(payNo, mchid, serialNo);
    }

    /**
     * 查询支付订单
     *
     * @param payNo     微信支付单号
     * @param mchid     商户号
     * @param serialNo  商户 API 证书序列号
     * @return
     * @throws Exception
     */
    @Override
    public Status queryByPayNo(String payNo, String mchid, String serialNo) throws Exception {
        IJPayHttpResponse v3Get = WxPayApi.v3(RequestMethod.GET, WxDomain.CHINA.getType(),
            String.format(WxApiType.ORDER_QUERY_BY_ID.getType(), payNo) + WxConstant.PARAM_MCHID_GET + mchid, mchid,
            serialNo, null, WxUtil.PRIVATE_KEY, "");
        System.out.println(v3Get);
        return null;
    }

    /**
     * 查询退款订单
     *
     * @param refundNo 退款单号
     * @return
     * @throws Exception
     */
    @Override
    public Status queryRefund(String refundNo) throws Exception {
        // 获取微信接口参数
        Map<String, ConfigVo> config = SystemParamBaseUtil.gets();
        String mchid = config.get(WxConstant.MCHID_CACHE_NAME).getVal();
        String serialNo = config.get(WxConstant.CA_SERIALNO_CACHE_NAME).getVal();

        // 拼装参数
        return queryRefund(refundNo, mchid, serialNo);
    }

    /**
     * 查询退款订单
     *
     * @param refundNo  退款单号
     * @param mchid     商户号
     * @param serialNo  商户 API 证书序列号
     * @return
     * @throws Exception
     */
    @Override
    public Status queryRefund(String refundNo, String mchid, String serialNo) throws Exception {
        IJPayHttpResponse v3Get = WxPayApi.v3(RequestMethod.GET, WxDomain.CHINA.getType(),
            String.format(WxApiType.ORDER_QUERY_BY_ID.getType(), refundNo), mchid, serialNo, null, WxUtil.PRIVATE_KEY,
            "");
        System.out.println(v3Get);
        return null;
    }

    /**
     * 单笔转账
     *
     * @param orderNo   订单号
     * @param total     转账金额
     * @param openid    微信的唯一标识
     * @param name      用户姓名
     * @param title     转账原因
     * @return
     * @throws Exception
     */
    @Override
    public WxResponseV2 withdrawDeposit(String orderNo, BigDecimal total, String openid, String name, String title)
        throws Exception {
        return WxPayV2Util.withdrawDeposit(orderNo, total, openid, name, title);
    }

    /**
     * 单笔转账
     *
     * @param orderNo   订单号
     * @param mchid     商户号
     * @param mchKey    微信商户密钥(API密钥)
     * @param appid     Appid
     * @param total     转账金额
     * @param openid    微信的唯一标识
     * @param name      用户姓名
     * @param title     转账原因
     * @return
     * @throws Exception
     */
    @Override
    public WxResponseV2 withdrawDeposit(String orderNo, String mchid, String mchKey, String appid, BigDecimal total,
        String openid, String name, String title) throws Exception {
        return WxPayV2Util.withdrawDeposit(orderNo, mchid, mchKey, appid, total, openid, name, title);
    }

    /**
     * 获取接口调用结果
     *
     * @param res
     * @return
     */
    @Override
    public Status getStatus(WxResponseV2 res) {
        return WxPayV2Util.getStatus(res);
    }

    /**
     * 查询转账订单
     *
     * @param orderNo 商户订单号
     * @return
     * @throws Exception
     */
    @Override
    public AlipayFundTransOrderQueryResponse query(String orderNo) throws Exception {
        // TODO
        // 获取支付宝接口参数
        AlipayFundTransOrderQueryModel param = new AlipayFundTransOrderQueryModel();
        param.setOutBizNo(orderNo);

        return AliPayApi.transferQueryToResponse(param);
    }

    /**
     * 判断一个对象是否是空(空对象)
     *
     * @param obj
     * @return
     */
    private static boolean isBlankObject(Object obj) {
        return StringUtil.isBlankObject(obj);
    }

    public static WxPayTestUtil getInstances() {
        if (isBlankObject(wxPayTestUtil)) {
            wxPayTestUtil = new WxPayTestUtil();
        }
        return wxPayTestUtil;
    }

}